extern crate argparse;
use std::io::Read;
use std::io::Write;

fn hand(src_stream: &std::net::TcpStream) -> Result<(), Box<dyn std::error::Error>> {
    println!("src {}", src_stream.peer_addr().unwrap());
    let mut src_reader = src_stream.try_clone()?;
    let mut src_writer = src_stream.try_clone()?;
    // 相关文章 https://zh.wikipedia.org/wiki/SOCKS#SOCKS5
    let mut buf: Vec<u8> = vec![0x00; 256];
    // 读取协议版本号(1字节)
    src_reader.read_exact(&mut buf[0..1])?;
    if buf[0] != 0x05 {
        panic!("unreachable!")
    }
    // 读取认证方法数量(1字节)
    src_reader.read_exact(&mut buf[0..1])?;
    // buf[0] as usize 是将一个字节(u8)的值转换为usize类型，这个转换过程实际上就是直接取字节的数值作为长度
    // 在Rust中，buf[0] as usize 是将一个字节(u8)的值转换为usize类型，这个转换过程实际上就是直接取字节的数值作为长度。具体解释如下：

    // buf[0] 是一个u8类型（无符号8位整数），取值范围0-255
    // as usize 将这个值原样转换为usize类型（平台相关的无符号整数，通常是32或64位）
    // 在SOCKS5协议中，buf[0]表示认证方法的数量(nmethods)，所以这个值自然就是需要读取的认证方法列表的长度
    // 如果buf[0] = 0x02 → 转换为usize就是2 → 表示后面有2个认证方法需要读取
    // 如果buf[0] = 0x00 → 转换为usize就是0 → 表示没有认证方法需要读取
    // u8 是一种无符号8位整数类型 0 到 255（2⁸-1）
    let nauth = buf[0] as usize;
    src_reader.read_exact(&mut buf[0..nauth])?;

    // buf[0..nauth] must contains 0x00
    // 0x00 表示无需认证
    if !buf[0..nauth].contains(&0x00) {
        panic!("unreachable!")
    }
    // 一次性写两个
    src_writer.write(&[0x05, 0x00])?;
    // 分开写
    // src_writer.write(&[0x05])?;
    // src_writer.write(&[0x00])?;

    println!("greeting done");
    // VER是SOCKS版本，这里应该是0x05；
    src_reader.read_exact(&mut buf[0..1])?;
    if buf[0] != 0x05 {
        panic!("unreachable!")
    }
    // CMD是SOCK的命令码：
    // 0x01表示CONNECT请求
    // 0x02表示BIND请求
    // 0x03表示UDP转发
    // RSV保留字段，值为0x00
    src_reader.read_exact(&mut buf[0..1])?;
    if buf[0] != 0x01 {
        panic!("unreachable!")
    }
    // RSV为保留字段，值为0x00；
    src_reader.read_exact(&mut buf[0..1])?;
    if buf[0] != 0x00 {
        panic!("unreachable!")
    }
    // ATYP为DST.ADDR类型：
    // 0x01 IPv4地址，DST.ADDR部分4字节长度
    // 0x03 域名，DST.ADDR部分的第一个字节为域名长度，DST.ADDR剩余的内容为域名，没有\0结尾。
    // 0x04 IPv6地址，16个字节长度
    src_reader.read_exact(&mut buf[0..1])?;
    let host = match buf[0] {
        0x01 => {
            println!("IPv4地址");
            // IPv4地址，DST.ADDR部分4字节长度
            src_reader.read_exact(&mut buf[0..4])?;
            std::net::Ipv4Addr::new(buf[0], buf[1], buf[2], buf[3]).to_string()
        }
        0x03 => {
            println!("域名");
            // 域名，DST.ADDR部分的第一个字节为域名长度
            src_reader.read_exact(&mut buf[0..1])?;
            let len = buf[0] as usize;
            src_reader.read_exact(&mut buf[0..len])?;
            String::from_utf8_lossy(&buf[0..len]).to_string() // baidu.com
        }
        0x04 => {
            println!("IPv6地址");
            // IPv6地址结构：

            // IPv6地址由8个16位段组成（共128位）
            // 每个段用16位（2字节）表示
            // 例如：2001:0db8:85a3:0000:0000:8a2e:0370:7334
            // IPv6地址，16个字节长度
            // 转换原理：
            // 每2个字节组合成一个16位段
            // << 8：将第一个字节左移8位（变成高8位）
            // |：与第二个字节（低8位）进行或运算
            // 例如：buf[0]=0x20和buf[1]=0x01 → 0x2001
            // 左移8位(<< 8)相当于将u8数值放大256倍
            // 这实际上是把1字节(u8)扩展为2字节(u16)的高位部分
            /***
            * 拆解密码锁：
            每个密码转盘需要2个数字组合（比如第一个转盘需要数字A和B）
            数字A相当于"十位数"，数字B相当于"个位数"
            组合方法：
            先把数字A左移8位（相当于在计算器中按"×256"）
            然后加上数字B
            例如：A=0x20（32），B=0x01（1）→ 32×256 + 1 = 8193（即0x2001）
            */
            src_reader.read_exact(&mut buf[0..16])?;
            // std::net::Ipv6Addr::new(
            //     ((buf[0x00] as u16) << 8) | (buf[0x01] as u16),
            //     ((buf[0x02] as u16) << 8) | (buf[0x03] as u16),
            //     ((buf[0x04] as u16) << 8) | (buf[0x05] as u16),
            //     ((buf[0x06] as u16) << 8) | (buf[0x07] as u16),
            //     ((buf[0x08] as u16) << 8) | (buf[0x09] as u16),
            //     ((buf[0x0a] as u16) << 8) | (buf[0x0b] as u16),
            //     ((buf[0x0c] as u16) << 8) | (buf[0x0d] as u16),
            //     ((buf[0x0e] as u16) << 8) | (buf[0x0f] as u16),
            // )
            // .to_string();
            // 等效简化写法（Rust标准库提供）
            std::net::Ipv6Addr::from([
                buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9],
                buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
            ])
            .to_string()
        }
        _ => {
            panic!("unreachable!");
        }
    };
    // BND.PORT为网络字节序表示的服务器绑定的端口
    // 端口号占2个字节
    /***
     * 这里解释端口号处理的原理：
    为什么端口号占2字节：
    网络协议规定端口号用16位(2字节)表示
    端口号范围：0-65535（2¹⁶-1）
    例如：80端口 = 0x0050，443端口 = 0x01BB
    为什么需要转换：

    网络传输使用大端序(Big-Endian)：高位字节在前
    例如端口8080（0x1F90）在网络中传输为：[0x1F, 0x90]
    但x86 CPU使用小端序，需要转换才能得到正确数值
    转换代码解析：

    let port = ((buf[0] as u16) << 8) | (buf[1] as u16);
    buf[0]是高8位（如0x1F）
    << 8左移8位变成0x1F00
    |合并低8位buf[1]（如0x90）
    结果：0x1F00 | 0x0090 = 0x1F90（即8080）
    实际例子：


    网络收到： [0x1F, 0x90]
    转换过程：
    0x1F << 8 = 0x1F00
    0x1F00 | 0x90 = 0x1F90 (8080)
    如果不转换会怎样：

    直接拼接会得到错误值：0x901F（36895）
    这就是网络字节序和主机字节序的区别
    这种转换是网络编程的标准做法，确保不同架构的机器都能正确解析端口号。
     */
    src_reader.read_exact(&mut buf[0..2])?;
    let port = ((buf[0] as u16) << 8) | (buf[1] as u16);
    println!("host:{:?}", host);

    let dst = format!("{}:{}", host, port);

    println!("dst:{}", dst);

    let dst_stream = std::net::TcpStream::connect(&dst)?;
    let mut dst_reader = dst_stream.try_clone()?;
    let mut dst_writer = dst_stream.try_clone()?;

    src_writer.write(&[0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])?;
    /***
     * 客户端 -> [代理] -> 目标服务器
     *       <-       <-
     */
    std::thread::spawn(move || {
        std::io::copy(&mut src_reader, &mut dst_writer).ok(); // 客户端→目标服务器
    });
    std::io::copy(&mut dst_reader, &mut src_writer).ok(); // 目标服务器→客户端

    Ok(())
}

use argparse::{ArgumentParser, Store};
fn main() {
    let mut c_listen = String::from("127.0.0.1:1080");
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("A simple socket5 proxy");
        ap.refer(&mut c_listen)
            .add_option(&["-l", "--listen"], Store, "listen address");
        ap.parse_args_or_exit();
    }
    println!("listen on {}", c_listen);
    let listener = std::net::TcpListener::bind(c_listen.as_str()).unwrap();
    for stream in listener.incoming() {
        match stream {
            Ok(data) => {
                std::thread::spawn(move || {
                    if let Err(err) = hand(&data) {
                        println!("hand error:{}", err);
                    }
                });
            }
            Err(err) => {
                println!("accept error:{}", err);
            }
        }
    }
}
